﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using rkShared;

namespace rkShared
{
  
	//Arazi Sınıfı
    class Terrain
    {
		
		public struct TerrainColor
		{
			public float r,g,b,a;
		}
		
		//arazinin noktaları
		private Vector3[] vertices;
		//noktaların renkleri(kayalık bolgeler icin)
		private TerrainColor[] rockColors;
		//arazinin normalleri
		private Vector3[] normals;
		//noktaları üçgenler haline getirmek için kullanılan indexler
		private int[] indices;
        
		//Size of the terrain this is the size of one edge
		//The terrain contains size*size vertices
		private int size;

		//Texture of the terrain
		rkShared.Texture grassTexture;
		rkShared.Texture rockTexture;
		
		public Terrain(int size,int seed,int hillCount,int minRadius,int maxRadius,int flatten)
		{
			this.size = size;
			
			//arazi size*size tane vertexten ve normalden oluşan kare bir arazi olacak
			vertices = new Vector3[size*size];
			normals = new Vector3[size*size];
			rockColors = new TerrainColor[size*size];
			//ucgenleri GL_TRIANGLE_STRIPS ile cizdigimiz için
			//size*(size-1)*2 tane index gerekiyor
			indices = new int[size*(size-1)*2];

			//vertex ve normalleri yarat ve yerleştir
			createMesh(size);

			//arazinin yükseltilerini oluştur
			generateHeightMap(size,seed,hillCount,minRadius,maxRadius,flatten);

			//arazinin normallerini hesapla
			calculateVertexNormals(size);

			//renkleri ayarla
			calculateColors();

			//load the textures of the terrain
			grassTexture = new Texture("Data/grass.bmp");
			rockTexture = new Texture("Data/rock.bmp");
			//snowTexture = new Texture("Data/snow.bmp");

		}

		private void calculateColors()
		{
			int cache = 0;
			for(int i=0; i<size; i++){
				for(int k=0; k<size; k++){
					cache = k + i * size;
					rockColors[cache].r = 
					rockColors[cache].g = 
					rockColors[cache].b = 1.0f;
					rockColors[cache].a = 0.0f;
										
					if(normals[cache].y < 0.95f)
						rockColors[cache].a = 1.0f;

				}
			}
		}
		//arazinin noktalarını yarat
		private void createMesh(int size)
		{
			int cache = 0;
			int index = 0;
			for(int i=0; i<size; i++){
				for(int k=0; k<size; k++){
					cache = k + i * size;
					vertices[cache] = new Vector3(k*10.0f,0.0f,-i*10.0f);
					normals[cache] = new Vector3();
					//colors[cache] = new TerrainColor();
					
					if(i < size - 1){
						indices[index++] = cache + size;
						indices[index++] = cache;
					}
				
				}
			}
		}
		//Araziyi oluşturan fonksiyon
		private void generateHeightMap(int size,int seed,int hillCount,int minRadius,int maxRadius,int flatten)
		{
			//seed ile rastgele sayı objesi yarat
			Random rnd = new Random(seed);
			int iVerticeCount = size*size;
			
			//add hills
			for(int i = 0; i < hillCount; i++){
				//tepenin yarıçapı				
				float fRadius = rnd.Next(minRadius,maxRadius);
				
				//Tepenin merkezi rastgele belirleniyor
				float x,y;
                x = rnd.Next( (int)-fRadius,(int)( size + fRadius) );
                y = rnd.Next( (int)-fRadius,(int)( size + fRadius) );
           
				float fRadiusSq = fRadius * fRadius;
				float fDistSq;
				float fHeight;

				int xMin = (int)(x - fRadius - 1);
				int xMax = (int)(x + fRadius + 1);

				if( xMin < 0 ) xMin = 0;
				if( xMax >= size ) xMax = size -1;

				int yMin = (int)(y - fRadius - 1);
				int yMax = (int)(y + fRadius + 1);

				if( yMin < 0 ) yMin = 0;
				if( yMax >= size ) yMax = size -1;

				//Raise the vertices based on distance from the center of hill
				for( int j = yMin; j <= yMax; ++j ){
					for( int k = xMin; k <= xMax; ++k ){
						// determine how far from the center of the hill this point is
						fDistSq = ( x - k ) * ( x - k ) + ( y - j ) * ( y - j );
						// determine the height of the hill at this point
						fHeight = fRadiusSq - fDistSq;

						// don't add negative hill values (i.e. outside the hill's radius)
						if( fHeight > 0 ){
							vertices[k+j*size].y += fHeight;
						}	
					}
				}
			}

			//normalize
			float fMin = vertices[0].y;
			float fMax = vertices[0].y;

			// find the min and max
			for( int i = 0; i < iVerticeCount; ++i ){
				float z = vertices[i].y;
				if( z < fMin ) fMin = z;
				if( z > fMax ) fMax = z;
			}
			// avoiding divide by zero (unlikely with floats, but just in case)
			if( fMax != fMin ){
				// divide every height by the maximum to normalize to ( 0.0, 1.0 )
				for( int i = 0; i < iVerticeCount; ++i )
					vertices[i].y = (vertices[i].y - fMin) / ( fMax - fMin );
			}
			//flatten
			if( flatten > 1 ){
				for( int i = 0; i < iVerticeCount; ++i ){
					float fFlat 	= 1.0f;
					float fOriginal	= vertices[i].y;

					//flatten as many times as desired
					for( int j = 0; j < flatten; ++j ){
						fFlat *= fOriginal;
					}
					fFlat *= 150;
					vertices[i].y = fFlat ;
				}
			}
			else{
				for( int i = 0; i < iVerticeCount; ++i ){
					float fOriginal	= vertices[i].y;
					fOriginal *= 150 ;
					vertices[i].y = fOriginal ;
				}
			}

		}
		//arazi vertexlerinin normallerini hesaplıyor
		private void calculateVertexNormals(int size)
		{
			Vector3[] pFaceNormals	= new Vector3 [(size-1)*(size-1)*2];
			Vector3[] pTempNormals	= new Vector3 [(size-1)*(size-1)*2];

			Vector3 vVector1 = new Vector3();
			Vector3 vVector2 = new Vector3();
			Vector3[] vPoly = new Vector3[3];
			vPoly[0] = new Vector3();
			vPoly[1] = new Vector3();
			vPoly[2] = new Vector3();
			Vector3 vNormal = new Vector3();
			int i = 0;
			int k = 0;
			int index=0;
			int cur = 0;
			int iCache = 0;

			for(i=0;i<size-1; ++i){
				iCache = i*size;
				for(k=0;k<size-1; ++k){
					cur = k+iCache;
					//first poly
					vPoly[0].Copy(vertices[cur+size]);
					vPoly[1].Copy(vertices[cur]);
					vPoly[2].Copy(vertices[cur+size+1]);

					vVector1 = vPoly[0] - vPoly[2];
					vVector2 = vPoly[2] - vPoly[1];	
					
					UMath.CrossProduct(vNormal,vVector2, vVector1);		
					pTempNormals[index] = new Vector3();
					pTempNormals[index].Copy(vNormal);
										
					UMath.Normalize(ref vNormal,vNormal);				

					pFaceNormals[index] = new Vector3();
					pFaceNormals[index] = vNormal;					
					index++;
					//second poly
					vPoly[0].Copy(vertices[cur+size+1]);
					vPoly[1].Copy(vertices[cur]);
					vPoly[2].Copy(vertices[cur+1]);

					vVector1 = vPoly[0] - vPoly[2];
					vVector2 = vPoly[2] - vPoly[1];

					UMath.CrossProduct(vNormal,vVector2, vVector1);		
					pTempNormals[index] = new Vector3();
					pTempNormals[index].Copy(vNormal);				
					UMath.Normalize(ref vNormal,vNormal);				
					
					pFaceNormals[index] = new Vector3();
					pFaceNormals[index] = vNormal;	
					index++;
				}
			}
			int tmpCache = 0;
			int shared = 0;
			int startz = 0;
			int startx = 0;

			Vector3 vSum = new Vector3(0,0,0);
			int iVerticeCount = size * size;

			for(int z=0; z<iVerticeCount; ++z){
				index=0;
				startz = (int)(z / size)-1;
				if(startz<0) startz = 0;
				for(i=startz; i<size-1; ++i){
					tmpCache = i * size;
					startx = (int)(z % size)-1;
					if(startx<0) startx=0;

					for(k=startx; k<size-1; ++k){

						iCache = k + tmpCache;
						index = (k + i * (size-1))<<1;
						if( iCache+size	== z||
							iCache			== z||
							iCache+size+1	== z){
								vSum += pTempNormals[index];
								shared++;
						}
						if( iCache+size+1	== z||
							iCache			== z||
							iCache+1			== z){
								vSum += pTempNormals[index+1];
								shared++;
						}
						if(shared>5)
							break;
					}
					if(shared>5)
						break;
				}

				UMath.Normalize(ref normals[z],vSum);

				vSum.x = 0;
				vSum.y = 0;
				vSum.z = 0;
				shared = 0;
			}
		}


		public Vector3[] Vertices
		{
			get{return vertices;}
		}
		public Vector3[] Normals
		{
			get{return normals;}
		}
		public TerrainColor[] RockColors
		{
			get{return rockColors;}
		}
		
		public int[] Indices
		{
			get{return indices;}
		}
		public int Size 
		{ 
			get{return size;}
			set{size = value;}
		}

		public rkShared.Texture GrassTexture
		{
			get{return grassTexture;}
		}
		public rkShared.Texture RockTexture
		{
			get{return rockTexture;}
		}
		
    }
}

        